package stack.era.domain.character;

import java.util.ArrayList;
import java.util.HashMap;

import stack.era.constants.CharacterAttributes;
import stack.era.constants.CharacterAttributes.Skill;
import stack.era.constants.CharacterAttributes.Stat;
import stack.era.constants.CrimeAttributes.CrimeType;
import stack.era.constants.DiseaseAttributes.DiseaseType;
import stack.era.domain.DomainObject;
import stack.era.domain.crime.Crime;
import stack.era.domain.disease.Disease;
import stack.era.domain.item.Item;
import stack.era.domain.job.Job;
import stack.era.domain.location.HiddenLocation;
import stack.era.domain.location.ResourceLocation;
import stack.era.domain.quest.Quest;
import stack.era.domain.town.Town;

public abstract class Character extends DomainObject {

	private static final long serialVersionUID = -453062991955922227L;

	protected int xp, level, fame, infamy, statPoints, skillPoints, health,
			fatigue, maxHealth, maxFatigue;

	protected HashMap<Stat, Integer> baseStats;
	protected HashMap<Stat, Integer> pointsAddedToStats;
	protected HashMap<Skill, Integer> baseSkills;
	protected HashMap<Skill, Integer> pointsAddedToSkills;
	protected HashMap<DiseaseType, ArrayList<Disease>> diseases;

	protected String townResidency;

	protected HashMap<CrimeType, ArrayList<Crime>> crimesCommitted;

	protected HashMap<String, Character> knownCharacters;
	protected HashMap<String, Town> knownTowns;
	protected HashMap<String, ResourceLocation> knownResourceLocations;
	protected HashMap<String, HiddenLocation> knownHiddenLocations;

	protected HashMap<String, Item> ownedItems;
	protected HashMap<String, Quest> assignedQuests;
	protected HashMap<String, Job> assignedJobs;

	/**
	 * Setup Default Character
	 * 
	 * @param newID
	 * @param newName
	 */
	public Character(String newName) {
		super(newName);

		this.xp = this.fame = this.infamy = this.skillPoints = this.statPoints = 0;
		this.health = this.maxHealth = this.fatigue = this.maxFatigue = 1;
		this.level = CharacterAttributes.STARTING_LEVEL;

		this.townResidency = CharacterAttributes.NO_TOWN;

		baseStats = new HashMap<Stat, Integer>();
		pointsAddedToStats = new HashMap<Stat, Integer>();
		baseSkills = new HashMap<Skill, Integer>();
		pointsAddedToSkills = new HashMap<Skill, Integer>();
		crimesCommitted = new HashMap<CrimeType, ArrayList<Crime>>();
		knownCharacters = new HashMap<String, Character>();
		knownTowns = new HashMap<String, Town>();
		knownResourceLocations = new HashMap<String, ResourceLocation>();
		knownHiddenLocations = new HashMap<String, HiddenLocation>();
		ownedItems = new HashMap<String, Item>();
		assignedQuests = new HashMap<String, Quest>();
		assignedJobs = new HashMap<String, Job>();
	}

	/**
	 * Returns the level the character should be at given the current amount of
	 * xp.
	 * 
	 * @param xp
	 * @return level
	 */
	public static int getLevelForXP(int xp) {
		int level = (int) ((Math.pow(Math.E, (Math.log((float) xp + 1) / 1.5f))) / 3f);
		if (level > CharacterAttributes.MAX_LEVEL) {
			level = CharacterAttributes.MAX_LEVEL;
		}
		if (level < 1) {
			level = 1;
		}
		return level;
	}

	/**
	 * Returns the amount of xp required to achieve a certain level. Mainly an
	 * exterior service.
	 * 
	 * @param level
	 * @return xp
	 */
	public int getXPRequiredForLevel(int level) {
		return (int) Math.pow((level * 3), 1.5);
	}

	/**
	 * Increase the xp of this character by the given amount
	 * 
	 * @param amount
	 */
	public void gainXP(int amount) {
		this.xp += amount;
		if (this.xp > CharacterAttributes.MAX_XP) {
			this.xp = CharacterAttributes.MAX_XP;
		}
		int newLevels = Character.getLevelForXP(this.xp) - this.level;
		for (int i = 0; i < newLevels; i++) {
			this.levelUp();
		}
	}

	protected abstract void levelUp();

	/**
	 * Add a crime to the list of committed crimes.
	 * 
	 * @param crime
	 */
	public void commitCrime(Crime crime) {
		crimesCommitted.get(crime.getType()).add(crime);
		this.infamy += crime.getType().getInfamy();
	}

	public void resolveCrime(Crime crime) {
		crimesCommitted.get(crime.getType()).remove(crime);
	}

	public void addPointsToStat(Stat stat, int amount) {
		this.pointsAddedToStats.put(stat, this.pointsAddedToStats.get(stat)
				+ amount);
		this.statPoints = this.statPoints - amount;
	}

	public void addPointsToSkill(Skill skill, int amount) {
		this.pointsAddedToSkills.put(skill, this.pointsAddedToSkills.get(skill)
				+ amount);
		this.skillPoints = this.skillPoints - amount;
	}

	public int getStat(Stat s) {
		try {
			int stat = baseStats.get(s) + pointsAddedToStats.get(s);
			if (stat > CharacterAttributes.MAX_STAT_POINTS) {
				stat = CharacterAttributes.MAX_STAT_POINTS;
			}
			return stat;
		} catch (NullPointerException e) {
			return 0;
		}
	}

	public int getSkill(Skill s) {
		try {
			int skill = baseSkills.get(s) + pointsAddedToSkills.get(s);
			if (skill > CharacterAttributes.MAX_SKILL_POINTS) {
				skill = CharacterAttributes.MAX_SKILL_POINTS;
			}
			return skill;
		} catch (NullPointerException e) {
			return 0;
		}
	}

	public int getLevel() {
		return level;
	}

	public int getXP() {
		return xp;
	}

	public int getStatPoints() {
		return statPoints;
	}

	public int getSkillPoints() {
		return skillPoints;
	}

	public int getHealth() {
		return health;
	}

	public int getFatigue() {
		return fatigue;
	}

	public int getMaxHealth() {
		return maxHealth;
	}

	public int getMaxFatigue() {
		return maxFatigue;
	}

	public ArrayList<String> getAllKnownTowns() {
		return new ArrayList<String>(knownTowns.keySet());
	}

	public ArrayList<String> getAllKnownResourceLocations() {
		return new ArrayList<String>(knownResourceLocations.keySet());
	}
	
	public ArrayList<String> getAllKnownHiddenLocations() {
		return new ArrayList<String>(knownHiddenLocations.keySet());
	}

}
